/************************************************************
** @brief   : xbutton_group
** @author  : vandoul
** @github  : https://gitee.com/vandoul
** @date    : 2022-02
** @version : v1.0.0
** @note    : xbutton_group.c
***********************************************************/

#include "xbutton_group.h"

struct _xbutton_group {
    int btn_num;
    xbutton_t btns_group;
    xbutton_get_ms_t fn_get_ms;
    xbutton_wait_event_t fn_wait_input;
    uint16_t long_press_time;
    uint16_t continous_press_time;
    uint16_t hold_press_time;
};

#pragma anon_unions

struct _xbutton {
    uint32_t level_change_stamp;
    uint16_t index;
    union {
        uint16_t flag_state;
        struct {
            uint16_t last_state:4;
            uint16_t trigger_level:1;
            uint16_t last_level:1;
            uint16_t used:1;
        };
    };
    xbutton_event_callback_t fn_evt_cb[XBUTTON_EVENT_MAX];
};


#define XBUTTON_OBJ_SIZE                sizeof(struct _xbutton)
#define XBUTTON_GROUP_OBJ_SIZE          sizeof(struct _xbutton_group)

#define XBUTTON_CONTINOUS_PRESS_TIME    300
#define XBUTTON_LONG_PRESS_TIME         500
#define XBUTTON_HOLD_PRESS_TIME         (2*XBUTTON_LONG_PRESS_TIME)

const static char *xbutton_event_str[XBUTTON_EVENT_MAX] = {
    [XBUTTON_DOWN] = "XBUTTON_DOWN",
    [XBUTTON_UP] = "XBUTTON_UP",
    [XBUTTON_DOUBLE] = "XBUTTON_DOUBLE",
    [XBUTTON_LONG] = "XBUTTON_LONG",
    [XBUTTON_LONG_FREE] = "XBUTTON_LONG_FREE",
    [XBUTTON_CONTINOUS] = "XBUTTON_CONTINOUS",
    [XBUTTON_CONTINOUS_FREE] = "XBUTTON_CONTINOUS_FREE",
    [XBUTTON_CONTINOUS_LONG] = "XBUTTON_CONTINOUS_LONG",
    [XBUTTON_CONTINOUS_LONG_FREE] = "XBUTTON_CONTINOUS_LONG_FREE",
    [XBUTTON_HOLD] = "XBUTTON_HOLD",
    [XBUTTON_HOLD_FREE] = "XBUTTON_HOLD_FREE",
};
const char *xbutton_event_code_to_string(xbutton_event_t e)
{
    if(IS_XBUTTON_EVENT(e)) {
        if(e== XBUTTON_EVENT_ALL) {
            return "XBUTTON_EVENT_ALL";
        } else {
            return xbutton_event_str[e];
        }
    }
    return "";
}
//group
xbutton_group_t xbutton_group_create(int btn_num, xbutton_get_ms_t get_ms, xbutton_wait_event_t wait_event)
{
    xbutton_group_t grp;
    grp = rt_malloc(XBUTTON_GROUP_OBJ_SIZE);
    if(grp == RT_NULL) {
        return RT_NULL;
    }
    rt_memset(grp, 0, XBUTTON_GROUP_OBJ_SIZE);
    grp->btns_group =  rt_malloc(XBUTTON_OBJ_SIZE * btn_num);
    if(grp->btns_group == RT_NULL) {
        rt_free(grp);
        return RT_NULL;
    }
    rt_memset(grp->btns_group, 0, XBUTTON_OBJ_SIZE * btn_num);
    for(int i=0; i<btn_num; i++) {
        grp->btns_group[i].index = i;
    }
    grp->btn_num = btn_num;
    grp->fn_get_ms = get_ms;
    grp->fn_wait_input = wait_event;
    grp->long_press_time = XBUTTON_LONG_PRESS_TIME;
    grp->hold_press_time = XBUTTON_HOLD_PRESS_TIME;
    grp->continous_press_time = XBUTTON_CONTINOUS_PRESS_TIME;
    return grp;
}

void xbutton_group_destroy(xbutton_group_t grp)
{
    if(grp == RT_NULL) {
        return ;
    }
    if(grp->btns_group != RT_NULL) {
        rt_memset(grp->btns_group, 0, XBUTTON_OBJ_SIZE * grp->btn_num);
        rt_free(grp->btns_group);
    }
    rt_memset(grp, 0, XBUTTON_GROUP_OBJ_SIZE);
    rt_free(grp);
}

uint32_t xbutton_group_get_long_press_time(xbutton_group_t grp)
{
    return grp->long_press_time;
}

int xbutton_group_set_long_press_time(xbutton_group_t grp, uint32_t t)
{
    if(grp == RT_NULL) {
        return -1;
    }
    grp->long_press_time = t;
    return 0;
}

uint32_t xbutton_group_get_continous_press_time(xbutton_group_t grp)
{
    return grp->continous_press_time;
}

int xbutton_group_set_continous_press_time(xbutton_group_t grp, uint32_t t)
{
    if(grp == RT_NULL) {
        return -1;
    }
    grp->continous_press_time = t;
    return 0;
}

uint32_t xbutton_group_get_hold_press_time(xbutton_group_t grp)
{
    return grp->hold_press_time;
}

int xbutton_group_set_hold_press_time(xbutton_group_t grp, uint32_t t)
{
    if(grp == RT_NULL) {
        return -1;
    }
    grp->hold_press_time = t;
    return 0;
}

static xbutton_t xbutton_group_get_button_by_index(xbutton_group_t grp, int index)
{
    if(grp == RT_NULL) {
        return RT_NULL;
    }
    if((grp->btn_num <= index) || (index < 0)) {
        return RT_NULL;
    }
    return &grp->btns_group[index];
}

static xbutton_t xbutton_group_get_unused_button_by_index(xbutton_group_t grp, int index)
{
    xbutton_t btn = xbutton_group_get_button_by_index(grp, index);
    if(btn->used) {
        btn = RT_NULL;
    }
    return btn;
}

static xbutton_t xbutton_group_get_unused_button(xbutton_group_t grp)
{
    if(grp == RT_NULL) {
        return RT_NULL;
    }
    xbutton_t btn_grp = grp->btns_group;
    for(int i=0; i<grp->btn_num; i++) {
        if(!btn_grp[i].used) {
            return &btn_grp[i];
        }
    }
    return RT_NULL;
}

int xbutton_group_add(xbutton_group_t grp, int index, uint16_t trigger_level)
{
    if(grp == RT_NULL) {
        return -1;
    }
    xbutton_t btn = RT_NULL;
    if(index < 0) {
        btn = xbutton_group_get_unused_button(grp);
    } else {
        btn = xbutton_group_get_unused_button_by_index(grp, index);
    }
    if(btn == RT_NULL) {
        return -1;
    }
    btn->trigger_level = trigger_level;
    btn->last_level = !trigger_level;
    btn->last_state = XBUTTON_UP;
    btn->level_change_stamp = grp->fn_get_ms();
    btn->used = true;
    return btn->index;
}

static void xbutton_group_delete_by_object(xbutton_t btn)
{
    btn->used = false;
    btn->level_change_stamp = 0;
    btn->trigger_level = 0;
}

int xbutton_group_delete(xbutton_group_t grp, int index)
{
    xbutton_t btn = xbutton_group_get_button_by_index(grp, index);
    if(btn == RT_NULL) {
        return -1;
    }
    xbutton_group_delete_by_object(btn);
    return 0;
}

int xbutton_group_add_button_event(xbutton_group_t grp, int index, xbutton_event_t evt, xbutton_event_callback_t cb)
{
    xbutton_t btn = xbutton_group_get_button_by_index(grp, index);
    if(btn == RT_NULL) {
        return -1;
    }
    if(!IS_XBUTTON_EVENT(evt)) {
        return -1;
    }
    if(evt == XBUTTON_EVENT_ALL) {
        for(int i=0; i<XBUTTON_EVENT_MAX; i++) {
            btn->fn_evt_cb[i] = cb;
        }
    } else {
        btn->fn_evt_cb[evt] = cb;
    }
    return 0;
}

inline static bool xbutton_state_is_up(int32_t state)
{
    if((state == XBUTTON_UP) 
        || (state == XBUTTON_LONG_FREE)
        || (state == XBUTTON_CONTINOUS_FREE)
        || (state == XBUTTON_CONTINOUS_LONG_FREE)
        || (state == XBUTTON_HOLD_FREE)) {
        return true;
    }
    return false;
}

static void xbutton_event_process(xbutton_group_t grp, int index, int level, uint32_t cur_ms)
{
    xbutton_t btn = &grp->btns_group[index];
    int state;
    if(level == -1) {
        state = btn->last_state;
        level = btn->last_level;
        if((level == btn->trigger_level) 
            && ((state == XBUTTON_DOWN) || (state == XBUTTON_LONG) || (state == XBUTTON_HOLD))) {
            state = XBUTTON_DOWN;
        } else {
            state = XBUTTON_UP;
        }
    } else if(btn->last_level != level) {
        if(level == btn->trigger_level) {
            state = XBUTTON_DOWN;
        } else {
            state = XBUTTON_UP;
        }
    } else {
        if(level == btn->trigger_level) {
            state = XBUTTON_DOWN;
        } else {
            state = XBUTTON_UP;
        }
    }
    uint32_t deltaT = cur_ms - btn->level_change_stamp;
    if(state == XBUTTON_DOWN) {
        state = btn->last_state;
        if(deltaT <= grp->continous_press_time) { // ->|__|<- (deltaT < continous_press_time)
            if(XBUTTON_UP == state) {
                if(btn->fn_evt_cb[XBUTTON_DOUBLE] == RT_NULL) {
                    state = XBUTTON_DOWN; // double to down
                } else {
                    state = XBUTTON_DOUBLE; // (last_state == XBUTTON_UP)
                }
            } else if(XBUTTON_DOUBLE == state) {
                state = XBUTTON_CONTINOUS; // (last_state == XBUTTON_DOUBLE)
            } else if(XBUTTON_CONTINOUS == state) {
                state = XBUTTON_CONTINOUS; // (last_state == XBUTTON_CONTINOUS)
            } else if(XBUTTON_DOWN != state) {
                state = XBUTTON_DOWN; // (last_state != XBUTTON_DOWN)
            } else {
                state = XBUTTON_UNKNOWN; // (last_state == XBUTTON_DOWN) #ignore press button.
            }
        } else if(xbutton_state_is_up(state)) { // button last state is up
            state = XBUTTON_DOWN;
        } else if(deltaT > grp->hold_press_time) { // ->|__|<- (deltaT > hold_press_time)
            state = XBUTTON_HOLD; // deltaT > hold_press_time #button is holding
        } else if(deltaT > grp->long_press_time) { // ->|__|<- (deltaT > long_press_time)
            state = XBUTTON_LONG; // deltaT > hold_press_time #button is long pressing
        } else {
            state = XBUTTON_UNKNOWN; // ignore 
        }
    } else if(state == XBUTTON_UP) {
        state = btn->last_state;
        if(state == XBUTTON_DOWN) { //down to up
            state = XBUTTON_UP;
        } else {
            // btn level is not changed.
            if(deltaT > grp->continous_press_time) { // ->|__|<- (deltaT > continous_press_time) # release button press state.
                if(state == XBUTTON_LONG) {
                    state = XBUTTON_LONG_FREE;
                } else if(state == XBUTTON_DOUBLE) {
                    state = XBUTTON_UP;
                } else if(state == XBUTTON_CONTINOUS) {
                    state = XBUTTON_CONTINOUS_FREE;
                } else if(state == XBUTTON_CONTINOUS_LONG) {
                    state = XBUTTON_CONTINOUS_LONG_FREE;
                } else if(state == XBUTTON_HOLD) {
                    state = XBUTTON_HOLD_FREE;
                } else if(state == XBUTTON_DOUBLE) {
                    state = XBUTTON_UP;
                } else {
                    state = XBUTTON_UNKNOWN;
                }
            } else {
                state = XBUTTON_UNKNOWN;
            }
        }
    } else {
        state = XBUTTON_UNKNOWN;
    }
    
    if(btn->last_level != level) { // level is changed, update stamp
        btn->last_level = level;
        btn->level_change_stamp = cur_ms;
    }
    
    if(state != XBUTTON_UNKNOWN) { // state is changed, call event process funcation.
        btn->last_state = state;
        xbutton_event_callback_t evt_cb = btn->fn_evt_cb[state];
        if(evt_cb) {
            evt_cb(index, (xbutton_event_t)state);
        }
    }
}

void xbutton_group_event_process(xbutton_group_t grp)
{
    if(grp == RT_NULL) {
        return ;
    }
    int level;
    int index = grp->fn_wait_input(&level); // get btn index and read btn level.
    uint32_t cur_ms = grp->fn_get_ms(); // get time stamp.
    xbutton_t btn = xbutton_group_get_button_by_index(grp, index);
    if((btn == RT_NULL) || (!btn->used)) {
        for(int i=0; i<grp->btn_num; i++) {
            btn = xbutton_group_get_button_by_index(grp, i);
            if((btn == RT_NULL) || (btn->used)) {
                xbutton_event_process(grp, i, -1, cur_ms);
            }
        }
    } else {
        xbutton_event_process(grp, index, level, cur_ms);
    }
}



